home *** CD-ROM | disk | FTP | other *** search
- /* Controller.m
- * Part of the Moon application for the NeXT computer.
- * Author: Geoffrey S. Knauth
- * Date: January 4, 1992
- *
- * Permission to copy this program is hereby granted under the terms
- * of the Free Software Foundation's GNU General Public License.
- */
-
- /* Initially generated by Interface Builder */
-
- #import <strings.h> /* strcpy */
- #import <time.h> /* struct tm, gmtime, etc. */
- #import <dpsclient/dpsclient.h> /* DPSAddTimedEntry */
- #import <appkit/Application.h> /* NX_BASETHRESHOLD */
- #import <appkit/Font.h> /* NX_IDENTITYMATRIX */
- #import <appkit/publicWraps.h> /* NXBeep */
- #import <objc/NXStringTable.h>
- #import "all.h"
- #import "Controller.h"
- #import "MoonView.h"
- #import "MoonIconView.h"
-
- static DPSTimedEntry te = 0;
-
- void tick(DPSTimedEntry teNumber, double now, void *userData);
-
- @implementation Controller
-
- - appDidHide :sender
- {
- [iconView display];
- return self;
- }
-
- - appDidInit:sender
- {
- NXRect iconRect = {{0.0, 0.0}, {64.0, 64.0}};
- long t; /* seconds since 0000 UTC 1/1/1970 */
- struct tm *tmLcl;
- char buf[80];
-
- nextNewMoon = 0.0; /* invalidate new moon info */
-
- /* make our icon view be the appIcon window's contentView */
- iconView = [[MoonIconView alloc] initFrame:&iconRect];
- [[[NXApp appIcon] setContentView:iconView] free];
-
- /* The nowButton should not be enabled until the user has gone into
- * time travel mode.
- */
- [nowButton setEnabled:NO];
-
- [statForm setTextFont:
- [Font newFont:"Ohlfs" size:10.0 matrix:NX_FLIPPEDMATRIX]];
-
- /* The local time title must be big enough to hold "Local (GMT+11:30)". */
- (void) time(&t);
- tmLcl = localtime(&t);
- sprintf(buf, "Local (%s)", tmLcl->tm_zone);
- [statForm setTitle:buf at:iLocalTime];
-
- /* Make sure the function tick (which sends us an update message)
- * is called on a regular basis.
- */
- te = DPSAddTimedEntry(TICK_SECONDS, tick, self, NX_BASETHRESHOLD);
-
- [self update]; /* update now, in case the wait is long */
-
- return self;
- }
-
- - appWillTerminate :sender
- {
- DPSRemoveTimedEntry(te);
- return self;
- }
-
- - getUserGmtTime :(int *)year :(int *)month :(int *)day
- :(int *)hour :(int *)minute :(int *)second
- {
- char buf[80], monthStr[20];
- int i, n, expected, hhmm;
-
- /* Here are the kinds of input we can accept:
- * hh:mm:ss dd mmmmm yyyy %d:%d:%d %d %s %d (6)
- * hh:mm dd mmmmm yyyy %d:%d %d %s %d (5)
- * hhmm dd mmmmm yyyy %d %d %s %d (4)
- * dd mmmmm yyyy %d %s %d (3)
- * mmmmm yyyy %s %d (2)
- * yyyy %d (1)
- */
- strcpy(buf, [travelText stringValue]);
- for (i = n = 0; i < 6; n = 0, i++) {
- expected = *hour = *minute = *second = *day = *month = *year = 0;
- switch (i) {
- case 0:
- expected = 6;
- n = sscanf(buf, "%d:%d:%d %d %s %d",
- hour, minute, second, day, monthStr, year);
- break;
- case 1:
- expected = 5;
- n = sscanf(buf, "%d:%d %d %s %d",
- hour, minute, day, monthStr, year);
- break;
- case 2:
- expected = 4;
- n = sscanf(buf, "%d %d %s %d", &hhmm, day, monthStr, year);
- break;
- case 3:
- expected = 3;
- n = sscanf(buf, "%d %s %d", day, monthStr, year);
- break;
- case 4:
- expected = 2;
- n = sscanf(buf, "%s %d", monthStr, year);
- *day = 1;
- break;
- case 5:
- expected = 1;
- n = sscanf(buf, "%d", year);
- *day = 1;
- strcpy(monthStr, [stringTable valueForStringKey:"1"]);
- break;
- }
- if (n && n == expected) break;
- }
- if (n) {
- *month = [self monthNumFromStr:monthStr];
- if (*month == 0) goto bad;
- if (expected == 4) {
- *hour = hhmm / 100;
- *minute = hhmm % 100;
- }
- return self;
- }
- bad:
- *hour = *minute = *second = *day = *month = *year = 0;
- return nil;
- }
-
- - (int)monthNumFromStr :(const char *)month
- {
- char monthBuf[2+1];
- int i;
-
- for (i = 12; i > 0; i--) {
- sprintf(monthBuf, "%d", i);
- if (strcmp(month, [stringTable valueForStringKey:monthBuf]) == 0)
- break;
- }
- return i;
- }
-
- - pause :sender
- {
- /* We need a pause method, so that when we are coming out of pause,
- * resuming to run, we can generate an update right away, without
- * making the user wait for the next official tick.
- */
- if ([sender state] == RUNNING) /* state changed before action sent */
- [self update];
-
- return self;
- }
-
- - returnToNow :sender
- {
- /* When the nowButton is disabled, we are not in time travel mode.
- * When are not in time travel mode, update gets its time from the system,
- * not from the user.
- */
- [sender setEnabled:NO]; /* sender is also nowButton */
- [pauseButton setEnabled:YES]; /* by definition we will run */
- nextNewMoon = 0.0; /* invalidate new moon info */
- [self update];
- return self;
- }
-
- - timeTravel :sender
- {
- /* When the nowButton is enabled, we are in time travel mode.
- * When are in time travel mode, update gets its time from the user,
- * not from the system.
- */
- [nowButton setEnabled:YES];
- [pauseButton setEnabled:NO]; /* by definition we will pause */
- nextNewMoon = 0.0; /* invalidate new moon info */
- [self update];
- return self;
- }
-
- - update
- {
- long t; /* seconds since 0000 UTC 1/1/1970 */
- static long gmtoff = -1L; /* timezone offset from UTC in secs */
- struct tm *tmUtc, *tmLcl; /* Unix UTC & local time structures */
- float p; /* moon phase: 0=new, 0.5=full */
- int lunation;
- double jd, aom, cphase, cdist, cangdia, csund, csuang, lptime;
- double phasar[5];
- double fakeJd; /* don't use for astro calculations! */
- char tbuf[80];
- char monthBuf[2+1]; /* "1" .. "12" */
- int yy, mm, dd, hh, mmm, ss; /* moon times */
- int year, month, day; /* our UTC times */
- int hour, minute, second; /* our UTC times */
- int lyear, lmonth, lday; /* our local times */
- int lhour, lminute, lsecond; /* our local times */
-
- if ([pauseButton state] == PAUSED) return nil;
-
- if ([nowButton isEnabled]) {
- /* We're traveling in time.
- * This is not the initial state, so gmtoff is legitimate.
- */
- if ([self getUserGmtTime
- :&year :&month :&day :&hour :&minute :&second] == nil)
- {
- NXBeep();
- [nowButton setEnabled:NO]; /* no time travel after all */
- return nil;
- }
- jd = ymdhmsToJtime(year, month, day, hour, minute, second);
- fakeJd = jd + (double)gmtoff / 86400.;
- } else {
- /* We're not traveling in time. Unix gives us our time info.
- * This is the initial state.
- */
- (void) time(&t);
- tmUtc = gmtime(&t);
- jd = jtime(tmUtc);
- year = tmUtc->tm_year + 1900;
- month = tmUtc->tm_mon + 1;
- day = tmUtc->tm_mday;
- hour = tmUtc->tm_hour;
- minute = tmUtc->tm_min;
- second = tmUtc->tm_sec;
- tmLcl = localtime(&t);
- fakeJd = jtime(tmLcl);
- if (gmtoff == -1L) {
- /* Save the time zone offset for later. We'll need it if the user
- * does any time traveling, because if we go too far into the past
- * or future, Unix time functions won't help us any more.
- */
- gmtoff = tmLcl->tm_gmtoff; /* remember offset is in seconds */
- }
- }
- p = (float)jdtophase(jd, &cphase, &aom, &cdist, &cangdia, &csund, &csuang);
-
- /* Draw the big image of the moon and shadow it. */
- [moonView setPhase:p];
- [moonView display];
-
- /* Draw the little icon image of the moon and shadow it. */
- [iconView setPhase:p];
- [iconView display];
-
- /* Update textual information */
-
- sprintf(tbuf, "%.5f", jd); /* 1979 January 1.0 <==> JD 2443874.5 */
- [statForm setStringValue:tbuf at:iJulianDate];
-
- sprintf(monthBuf, "%d", month);
- sprintf(tbuf, "%02d:%02d:%02d %d %s %d",
- hour, minute, second, day,
- [stringTable valueForStringKey:monthBuf], year);
- [statForm setStringValue:tbuf at:iUniversalTime];
-
- jyear(fakeJd, &lyear, &lmonth, &lday);
- jhms(fakeJd, &lhour, &lminute, &lsecond);
- sprintf(monthBuf, "%d", lmonth);
- sprintf(tbuf, "%02d:%02d:%02d %d %s %d",
- lhour, lminute, lsecond, lday,
- [stringTable valueForStringKey:monthBuf], lyear);
- [statForm setStringValue:tbuf at:iLocalTime];
-
- sprintf(tbuf, "%d%% %s, %s",
- (int) rint(cphase * 100),
- [stringTable valueForStringKey:"visible"],
- p < 0.5 ?
- [stringTable valueForStringKey:"waxing"]
- : [stringTable valueForStringKey:"waning"]);
- [statForm setStringValue:tbuf at:iMoonPhase];
-
- /* Some information about the Moon */
-
- sprintf(tbuf, "%dd %dh %dm",
- (int) aom, (int) (24 * (aom - floor(aom))),
- ((int) (1440 * (aom - floor(aom)))) % 60);
- [statForm setStringValue:tbuf at:iAgeOfMoon];
-
- sprintf(tbuf, "%ld km, %.1f %s",
- (long) cdist, cdist / earthrad,
- [stringTable valueForStringKey:"earthRadii"]);
- [statForm setStringValue:tbuf at:iMoonDistance];
-
- sprintf(tbuf, "%.4f %s", cangdia,
- [stringTable valueForStringKey:"degrees"]);
- [statForm setStringValue:tbuf at:iMoonSubtends];
-
- /* Information about the Sun */
-
- sprintf(tbuf, "%.0f km, %.3f AU", csund, csund / sunsmax);
- [statForm setStringValue:tbuf at:iSunDistance];
-
- sprintf(tbuf, "%.4f %s", csuang,
- [stringTable valueForStringKey:"degrees"]);
- [statForm setStringValue:tbuf at:iSunSubtends];
-
- /* Calculate times of phases of this lunation. This is sufficiently
- * time-consuming that we only do it once a month, or when we begin or
- * return from time travel. If we've just changed the time travel mode,
- * nextNewMoon will be zero.
- */
- if (jd > nextNewMoon) {
- phasehunt(jd, phasar);
- lptime = phasar[0];
- lunation = floor(((lptime + 7) - lunatbase) / synmonth) + 1;
- jyear(lptime, &yy, &mm, &dd);
- jhms(lptime, &hh, &mmm, &ss);
- sprintf(monthBuf, "%d", mm);
- sprintf(tbuf, "%02d:%02d UTC %d %s %d", hh, mmm, dd,
- [stringTable valueForStringKey:monthBuf], yy);
- [statForm setStringValue:tbuf at:iLastNewMoon];
-
- sprintf(tbuf, "%d", lunation);
- [statForm setStringValue:tbuf at:iThisLunation];
-
- lptime = phasar[1];
- jyear(lptime, &yy, &mm, &dd);
- jhms(lptime, &hh, &mmm, &ss);
- sprintf(monthBuf, "%d", mm);
- sprintf(tbuf, "%02d:%02d UTC %d %s %d", hh, mmm, dd,
- [stringTable valueForStringKey:monthBuf], yy);
- [statForm setStringValue:tbuf at:iFirstQuarter];
-
- lptime = phasar[2];
- jyear(lptime, &yy, &mm, &dd);
- jhms(lptime, &hh, &mmm, &ss);
- sprintf(monthBuf, "%d", mm);
- sprintf(tbuf, "%02d:%02d UTC %d %s %d", hh, mmm, dd,
- [stringTable valueForStringKey:monthBuf], yy);
- [statForm setStringValue:tbuf at:iFullMoon];
-
- lptime = phasar[3];
- jyear(lptime, &yy, &mm, &dd);
- jhms(lptime, &hh, &mmm, &ss);
- sprintf(monthBuf, "%d", mm);
- sprintf(tbuf, "%02d:%02d UTC %d %s %d", hh, mmm, dd,
- [stringTable valueForStringKey:monthBuf], yy);
- [statForm setStringValue:tbuf at:iLastQuarter];
-
- nextNewMoon = phasar[4];
- jyear(nextNewMoon, &yy, &mm, &dd);
- jhms(nextNewMoon, &hh, &mmm, &ss);
- sprintf(monthBuf, "%d", mm);
- sprintf(tbuf, "%02d:%02d UTC %d %s %d", hh, mmm, dd,
- [stringTable valueForStringKey:monthBuf], yy);
- [statForm setStringValue:tbuf at:iNextNewMoon];
-
- sprintf(tbuf, "%d", lunation + 1);
- [statForm setStringValue:tbuf at:iNextLunation];
- }
- return self;
- }
-
- void tick(DPSTimedEntry teNumber, double now, void *userData)
- {
- Controller *self = userData; /* this trick lets C do some Obj-C */
-
- /* We only came here because of the interface from the DPSTimedEntry.
- * Now let's be more objective.
- */
- if ([self->nowButton isEnabled]) {
- /* We're in time travel mode.
- * That means only the user should be able to generate update events.
- * While in this mode, ticks will be ignored.
- * We do this so that the user may finish typing a time travel date
- * and tell us when to read it, otherwise we are likely to read it
- * prematurely.
- */
- return;
- }
- [self update];
- }
-
- @end
-